;; host/udp.scm - (c) rohan drape, 2003-2005

;; Mechanism for sending and receiving byte packets over a UDP
;; connection.  See equivalent mechanism at tcp.scm.

(define-structure udp-client (thread-token socket))

;; Make a connected UDP client that will communicate with a server at
;; `address'.  The port that the UDP socket is bound to is randomly
;; generated.  Incoming datagrams are handled by `receiver', which is
;; a two argument procedure accepting a <network-address> and a
;; <u8vector>.

(defineV (make-udp-client address receiver)
  (let* ((host-address (network-address-host-address address))
	 (port-number (network-address-port-number address))
	 (host-name (host-address->host-name host-address))
	 (socket (udp-open-socket)))
    ;; Randomly bind socket, this may fail.  The socket must be bound
    ;; before udp-receive! can be called, and we wish to enter the
    ;; listener loop immediately.  The better way to bind is to call
    ;; udp-send-to which will not fail to make a random binding.
    (udp-bind! socket host-name (randomI-left-right 64500 64900))
    (udp-connect! socket host-name port-number)
    (make-udp-client*
     (make-thread*
      (lambda ()
	(let ((input-bytes (make-bytes 8192)))
	  (let loop ()
	    (receive 
	     (size host-name port-number)
	     (udp-receive! socket input-bytes)
	     ;; Because, absurdly, udp-receive! passes the host
	     ;; address as a byte string we make this a connection
	     ;; based interface, which allows the address to be
	     ;; cached.
	     (receiver address 
		       (bytes->u8vector (subbytes input-bytes 0 size)))
	     (loop))))))
     socket)))

;; Send the <u8vector> `packet' to the port at `u'.

(define (udp-client-send u packet)
  (udp-send (udp-client-socket u) (u8vector->bytes packet)))

;; Close connection at `u'.

(define (udp-client-stop u)
  (thread-terminate! (udp-client-thread-token u))
  (udp-close (udp-client-socket u)))
